// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Media.Media3D;
using System.Diagnostics;

using Microsoft.WindowsAPICodePack.DirectX.Direct3D10;
using Microsoft.WindowsAPICodePack.DirectX.Controls;
using Microsoft.WindowsAPICodePack.DirectX.DXGI;
using Microsoft.WindowsAPICodePack.DirectX.Direct3DCommon;
using Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities;

namespace Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities
{
    /// <summary>
    /// This is a camera whose position can be changed with the mouse. Pan operations 
    /// mapped to dragging with the left mouse button depressed( x+y axis), and by
    /// using the mouse wheel (z axis). ArcBall operations are mapped to dragging with
    /// the right mouse button is depressed (x+y axis). z-axis rotation is not directly
    /// supported.
    /// </summary>
    public class ArcPanCamera
    {
        public PerspectiveCamera camera;

        private DirectControl control;

        double xTranslationDelta = 0;
        double yTranslationDelta = 0;
        double zTranslation = 0;
        double xRotationDelta = 0;
        double yRotationDelta = 0;
        System.Drawing.Point mouseDownAt;
        MouseButtons buttonDown = MouseButtons.None;

        Transform3DGroup ctg;
        AxisAngleRotation3D xCameraRotation;
        AxisAngleRotation3D yCameraRotation;
        TranslateTransform3D xyzCameraTranslate;

        Stopwatch sw;
        double lastRenderTime = 0;

        /// <summary>
        /// Builds the ArcBall object
        /// </summary>
        /// <remarks>This must be initialized on the render thread</remarks>
        /// <param name="control"></param>
        public ArcPanCamera(DirectControl control)
        {
            this.control = control;
            control.MouseDown += new MouseEventHandler(control_MouseDown);
            control.MouseUp += new MouseEventHandler(control_MouseUp);
            control.MouseMove += new MouseEventHandler(control_MouseMove);
            control.MouseWheel += new MouseEventHandler(control_MouseWheel);

            camera =
                new PerspectiveCamera(
                    new Point3D(0.0, 3.0, -3.0),
                    new Vector3D(0.0, 1.0, 0.0),
                    new Vector3D(0.0, 1.0, 0.0),
                    45);
            camera.NearPlaneDistance = 0.1;
            camera.FarPlaneDistance = 100.0;

            yCameraRotation = new AxisAngleRotation3D(new Vector3D(1, 0, 0), -yRotationDelta);
            xCameraRotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), xRotationDelta);
            xyzCameraTranslate = new TranslateTransform3D( xTranslationDelta, yTranslationDelta, zTranslation );

            ctg = new Transform3DGroup();
            ctg.Children.Add(new RotateTransform3D(yCameraRotation));
            ctg.Children.Add(new RotateTransform3D(xCameraRotation));
            ctg.Children.Add( xyzCameraTranslate );

            sw = new Stopwatch();
        }

        /// <summary>
        /// Returns the camera (view*projection) matrix
        /// </summary>
        /// <returns>A matrix as an array of floats siutable for </returns>
        public float[] GetArcBallMatrix()
        {
            double ellapsed = sw.ElapsedMilliseconds;
            double timingFactor = (ellapsed - lastRenderTime) / 1000.0;
            lastRenderTime = ellapsed;

            yCameraRotation.Angle -= (yRotationDelta * timingFactor);
            xCameraRotation.Angle += (xRotationDelta * timingFactor);
            xyzCameraTranslate.OffsetX += (xTranslationDelta / 10.0 * timingFactor);
            xyzCameraTranslate.OffsetY += (yTranslationDelta / 10.0 * timingFactor);
            xyzCameraTranslate.OffsetZ = zTranslation;
            camera.Transform = new MatrixTransform3D(ctg.Value);
            return camera.ToArrayLH((double)control.Width / (double)control.Height);
        }

        void control_MouseWheel(object sender, MouseEventArgs e)
        {
            zTranslation += ((double)e.Delta / 100.0);
        }

        void control_MouseMove(object sender, MouseEventArgs e)
        {
            if (buttonDown == MouseButtons.Left)
            {
                xTranslationDelta = (double)(e.Location.X - mouseDownAt.X);
                yTranslationDelta = (double)(mouseDownAt.Y - e.Location.Y);
            }
            else if (buttonDown == MouseButtons.Right)
            {
                xRotationDelta = (double)(e.Location.X - mouseDownAt.X);
                yRotationDelta = (double)(mouseDownAt.Y - e.Location.Y);
            }
        }

        void control_MouseUp(object sender, MouseEventArgs e)
        {
            buttonDown = MouseButtons.None;
            yRotationDelta = 0;
            xRotationDelta = 0;
            xTranslationDelta = 0;
            yTranslationDelta = 0;
            sw.Stop();
        }

        void control_MouseDown(object sender, MouseEventArgs e)
        {
            Point ptCenter = new Point(control.ClientRectangle.Width / 2, control.ClientRectangle.Height / 2);
            Point ptScreen = control.PointToScreen(ptCenter);
            Cursor.Position = ptScreen;

            buttonDown = e.Button;
            mouseDownAt = ptCenter;
            lastRenderTime = 0;
            sw.Reset();
            sw.Start();
        }

    }
}
